﻿using System;
using System.Collections.Generic;
using System.Linq;
using Cosmos.Logging.Core.Extensions;
using Cosmos.Logging.Events;
using Cosmos.Logging.MessageTemplates.PresetTemplates;
using SqlSugar;

namespace Cosmos.Logging.Extensions.SqlSugar.Core {
    internal static class SqlSugarAopActivation {
        private const string TimestampKey = "COSMOSLOOPS::logging-extra-sqlsugar";

        public static void RegisterToSqlSugar(
            SimpleClient client, SqlSugarInterceptorDescriptor descriptor,
            Action<string, SugarParameter[]> executingAct = null,
            Func<string, SugarParameter[], object> executedAct = null,
            Func<Exception, object> errorAct = null,
            Func<string, LogEventLevel, bool> filter = null) {
            RegisterToSqlSugar(client.AsSugarClient() as SqlSugarClient, descriptor, executingAct, executedAct, errorAct, filter);
        }

        public static void RegisterToSqlSugar(
            SqlSugarClient client,
            SqlSugarInterceptorDescriptor descriptor,
            Action<string, SugarParameter[]> executingAct = null,
            Func<string, SugarParameter[], object> executedAct = null,
            Func<Exception, object> errorAct = null,
            Func<string, LogEventLevel, bool> filter = null) {

            if (client is null) throw new ArgumentNullException(nameof(client));
            if (descriptor is null) throw new ArgumentNullException(nameof(descriptor));

            var aop = client.Aop;

            aop.OnLogExecuting = descriptor.ExposeExecutingInterceptor;
            aop.OnLogExecuted = descriptor.ExposeExecutedInterceptor;
            aop.OnError = descriptor.ExposeErrorInterceptor;

            Func<string, LogEventLevel, bool> localFilter = (s, l) => (descriptor.ExposeGlobalFilter?.Invoke(s, l) ?? true) && (filter?.Invoke(s, l) ?? true);

            aop.OnLogExecuting = (sql, @params) => InternalExecutingOpt(client, sql, @params, executingAct);
            aop.OnLogExecuted = (sql, @params) => InternalExecutedOpt(descriptor, client, sql, @params, executedAct, localFilter);
            aop.OnError = exception => InternalErrorOpt(descriptor, client, exception, errorAct, localFilter);
        }

        private static void InternalExecutingOpt(SqlSugarClient client, string sql, SugarParameter[] @params, Action<string, SugarParameter[]> executingAct = null) {
            if (client.TempItems is null) client.TempItems = new Dictionary<string, object>();
            executingAct?.Invoke(sql, @params);
            client.TempItems.Add(TimestampKey, DateTime.Now);
        }

        private static void InternalExecutedOpt(
            SqlSugarInterceptorDescriptor descriptor,
            SqlSugarClient client,
            string sql,
            SugarParameter[] @params,
            Func<string, SugarParameter[], object> executedAct = null,
            Func<string, LogEventLevel, bool> filter = null) {

            var ms = 0D;

            if (client.TempItems.TryGetValue(TimestampKey, out var startStamp) && startStamp is DateTime stamp) {
                client.TempItems.Remove(TimestampKey);
                ms = DateTime.Now.Subtract(stamp).TotalMilliseconds;
            }

            object loggingParams;
            var userInfo = executedAct?.Invoke(sql, @params) ?? string.Empty;
            var logger = descriptor.ExposeLoggingServiceProvider.GetLogger<SqlSugarClient>(filter, LogEventSendMode.Automatic, descriptor.RenderingOptions);

            if (ms > 1000) {
                if (!logger.IsEnabled(LogEventLevel.Warning))
                    return;

                var logTrack = LogTrack.Create(client.ContextID, EventIdKeys.LongTimeExecuted);
                loggingParams = new {
                    OrmName = Constants.SinkKey,
                    ContextId = client.ContextID,
                    Sql = sql,
                    SqlParams = @params.Select(param => new DbParam(param.ParameterName, param.Value, param.DbType)).ToList(),
                    UsedTime = ms,
                    UserInfo = userInfo
                };
                logger.LogWarning(logTrack, OrmTemplateStandard.LongNormal, loggingParams);
            } else {
                if (!logger.IsEnabled(LogEventLevel.Information))
                    return;

                var logTrack = LogTrack.Create(client.ContextID, EventIdKeys.Executed);
                loggingParams = new {
                    OrmName = Constants.SinkKey,
                    ContextId = client.ContextID,
                    Sql = sql,
                    UsedTime = ms,
                    UserInfo = userInfo
                };
                logger.LogInformation(logTrack, OrmTemplateStandard.Normal, loggingParams);
            }
        }

        private static void InternalErrorOpt(
            SqlSugarInterceptorDescriptor descriptor,
            SqlSugarClient client,
            Exception exception,
            Func<Exception, object> errorAct = null,
            Func<string, LogEventLevel, bool> filter = null) {

            var ms = 0D;

            if (client.TempItems.TryGetValue(TimestampKey, out var startStamp) && startStamp is DateTime stamp) {
                client.TempItems.Remove(TimestampKey);
                ms = DateTime.Now.Subtract(stamp).TotalMilliseconds;
            }

            object userInfo = errorAct?.Invoke(exception) ?? string.Empty;
            var logger = descriptor.ExposeLoggingServiceProvider.GetLogger<SqlSugarClient>(filter, LogEventSendMode.Automatic, descriptor.RenderingOptions);

            if (!logger.IsEnabled(LogEventLevel.Error))
                return;

            var logTrack = LogTrack.Create(client.ContextID, EventIdKeys.Error);
            var realException = exception.Unwrap();
            var loggingParams = new {
                OrmName = Constants.SinkKey,
                ContextId = client.ContextID,
                Sql = "unknown",
                SqlParams = "unknown",
                ExceptionType = exception.GetType(),
                ExceptionMessage = exception.Message,
                RealExceptionType = realException.GetType(),
                RealExceptionMessage = realException.Message,
                UsedTime = ms,
                UserInfo = userInfo
            };
            logger.LogError(logTrack, exception, OrmTemplateStandard.Error, loggingParams);
        }
    }
}